package operation.util;

import cn.hutool.core.collection.CollectionUtil;
import operation.constant.AppErrorCode;
import operation.exception.AppException;
import operation.modules.operation.dto.ExportCustomFieldDTO;
import operation.modules.operation.dto.ExportFieldDTO;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFFont;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExcelUtil {

    public static CellStyle getHeaderStyle(SXSSFWorkbook workbook) {
        // 设置字体
        XSSFFont font = (XSSFFont) workbook.createFont();
        // 设置字体大小
        font.setFontHeightInPoints((short) 11);
        // 字体加粗
        font.setBold(true);
        // 设置字体名字
        font.setFontName("等线");
        // 设置样式
        XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle();
        // 设置底边框
        style.setBorderBottom(BorderStyle.THIN);
        // 设置底边框颜色
        style.setBottomBorderColor(IndexedColors.BLACK.index);
        // 设置左边框
        style.setBorderLeft(BorderStyle.THIN);
        // 设置左边框颜色
        style.setLeftBorderColor(IndexedColors.BLACK.index);
        // 设置右边框
        style.setBorderRight(BorderStyle.THIN);
        // 设置右边框颜色
        style.setRightBorderColor(IndexedColors.BLACK.index);
        // 设置顶边框
        style.setBorderTop(BorderStyle.THIN);
        // 设置顶边框颜色
        style.setTopBorderColor(IndexedColors.BLACK.index);
        // 设置填充背景颜色
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        // 设置填充模式
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        //在样式用应用设置的字体
        style.setFont(font);
        // 设置自动换行
        style.setWrapText(false);
        // 设置水平对齐的样式为居中对齐
        style.setAlignment(HorizontalAlignment.CENTER);
        // 设置垂直对齐的样式为居中对齐
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        return style;
    }

    public static CellStyle getCellStyle(SXSSFWorkbook workbook) {
        // 设置字体
        XSSFFont font = (XSSFFont) workbook.createFont();
        // 设置字体大小
        // font.setFontHeightInPoints((short)11);
        // 字体加粗
        // font.setBold(true);
        // 设置字体名字
        font.setFontName("等线");
        // 设置样式
        XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle();
        // 设置底边框
        style.setBorderBottom(BorderStyle.THIN);
        // 设置底边框颜色
        style.setBottomBorderColor(IndexedColors.BLACK.index);
        // 设置左边框
        style.setBorderLeft(BorderStyle.THIN);
        // 设置左边框颜色
        style.setLeftBorderColor(IndexedColors.BLACK.index);
        // 设置右边框
        style.setBorderRight(BorderStyle.THIN);
        // 设置右边框颜色
        style.setRightBorderColor(IndexedColors.BLACK.index);
        // 设置顶边框
        style.setBorderTop(BorderStyle.THIN);
        // 设置顶边框颜色
        style.setTopBorderColor(IndexedColors.BLACK.index);
        // 在样式用应用设置的字体
        style.setFont(font);
        // 设置自动换行
        style.setWrapText(false);
        // 设置水平对齐的样式为居中对齐
        style.setAlignment(HorizontalAlignment.CENTER);
        // 设置垂直对齐的样式为居中对齐
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        return style;
    }

    /**
     * 导出Excel
     *
     * @param sheetName    sheetName
     * @param fieldDTOList 导出的属性
     * @param dataList     导出的数据（不能为空）
     * @param response     response
     * @param <T>          导出数据实体类
     */
    public static <T> void export(String sheetName,
                                  List<ExportFieldDTO> fieldDTOList,
                                  List<T> dataList,
                                  HttpServletResponse response) {
        // 内存中缓存记录行数
        int rowAccessWindowSize = 100;
        // 创建工作簿
        // rowAccessWindowSize为-1时表示关闭自动写入磁盘功能,所有数据将放在内存中
        SXSSFWorkbook workBook = new SXSSFWorkbook(rowAccessWindowSize);
        // 获取表头样式
        CellStyle headerStyle = ExcelUtil.getHeaderStyle(workBook);
        CellStyle cellStyle = ExcelUtil.getCellStyle(workBook);
        // 设置表头
        SXSSFSheet sheet = workBook.createSheet(sheetName);
        // 表头单元行
        SXSSFRow headerRow = sheet.createRow(0);
        // 数据单元行
        SXSSFRow dataRow;
        // 设置自适应列宽（设置自适应列宽会大大降低效率,故采用计算方式进行）
        // sheet.autoSizeColumn(1, true);
        // 存储最大列宽
        Map<Integer, Integer> maxWidth = new HashMap<>();
        // 设置表头
        for (int i = 0; i < fieldDTOList.size(); i++) {
            Cell headerRowCell = headerRow.createCell(i);
            headerRowCell.setCellValue(fieldDTOList.get(i).getName());
            headerRowCell.setCellStyle(headerStyle);
            // 设置表头最大宽度（多设置4个字节的宽度以提高用户体验）
            maxWidth.put(i, headerRow.getCell(i).getStringCellValue().getBytes().length * 256 + 4 * 256);
        }
        if (CollectionUtil.isEmpty(dataList)) {
            throw new AppException(AppErrorCode.MSG_PARAMETER_ERROR, AppErrorCode.PARAMETER_ERROR);
        }
        T t = dataList.get(0);
        Field[] fields = t.getClass().getDeclaredFields();
        // 设置表格数据
        for (int rowNum = 0; rowNum < dataList.size(); rowNum++) {
            dataRow = sheet.createRow(rowNum + 1);
            // 记录这一行的每列的长度，用于计算列宽
            List<Object> columnValueList = new ArrayList<>();
            // 获取当前行的数据
            T entity = dataList.get(rowNum);
            for (int cellNum = 0; cellNum < fieldDTOList.size(); cellNum++) {
                // 设置表格数据
                for (Field f : fields) {
                    f.setAccessible(true);
                    // 导出选中的字段（属性名与导出实体的属性名一致时导出该条数据）
                    if (fieldDTOList.get(cellNum).getField().equals(f.getName())) {
                        // 单元格是否需要自定义输出
                        try {
                            // 创建单元格并设置样式
                            Cell cell = dataRow.createCell(cellNum);
                            cell.setCellStyle(cellStyle);
                            if (fieldDTOList.get(cellNum).getCustom()) {
                                List<ExportCustomFieldDTO> customFieldDTOList = fieldDTOList.get(cellNum).getCustomList();
                                if (customFieldDTOList == null || CollectionUtil.isEmpty(customFieldDTOList)) {
                                    return;
                                }
                                for (int i = 0; i < customFieldDTOList.size(); i++) {
                                    // 当前数据的属性值和自定义属性列表的中的某个值相等时设为自定义的值
                                    if (customFieldDTOList.get(i).getValue().equals(f.get(entity))) {
                                        cell.setCellValue(customFieldDTOList.get(i).getLabel());
                                        columnValueList.add("");
                                        break;
                                        // 不相等则设置为空
                                    } else if (i == customFieldDTOList.size() - 1) {
                                        cell.setCellValue("");
                                        columnValueList.add("");
                                    }
                                }
                                // 值为null时，设为空
                            } else if (f.get(entity) == null) {
                                cell.setCellValue("");
                                columnValueList.add("");
                            } else {
                                if ("java.util.Date".equals(f.get(entity).getClass().getName())) {
                                    Date date = (Date) f.get(entity);
                                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                                    cell.setCellValue(sdf.format(date));
                                    columnValueList.add(sdf.format(date));
                                } else {
                                    cell.setCellValue(f.get(entity).toString());
                                    columnValueList.add(f.get(entity).toString());
                                }
                            }
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            // 每当行数达到设置的值就刷新数据到硬盘,以清理内存
            if (rowNum % rowAccessWindowSize == 0) {
                // 保留最后100行并输出所有其他行
                try {
                    sheet.flushRows(rowAccessWindowSize);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // fieldDTOList和columnValueList的size总是相等的
            // 设置表头最大宽度（多设置4个字节的宽度以提高用户体验）
            for (int i = 0; i < columnValueList.size(); i++) {
                int length = columnValueList.get(i).toString().getBytes().length * 256 + 4 * 256;
                maxWidth.put(i, Math.max(length, maxWidth.get(i)));
            }
        }
        // 设置列宽
        for (int i = 0; i < fieldDTOList.size(); i++) {
            // setColumnWidth中的width参数如果大于255*256就会抛出异常，因此这里做了处理
            sheet.setColumnWidth(i, maxWidth.get(i) > 255 * 256 ? 255 * 256 : maxWidth.get(i));
        }
        try {
            // 将数据写到输出流
            OutputStream out = response.getOutputStream();
            workBook.write(out);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 删除临时文件
        workBook.dispose();
    }

    private ExcelUtil() {
    }
}
